Package g4p_controls

Source Code of g4p_controls.StyledString$AttributeRun

/*
  Part of the GUI for Processing library
    http://www.lagers.org.uk/g4p/index.html
  http://gui4processing.googlecode.com/svn/trunk/

  Copyright (c) 2008-13 Peter Lager

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General
  Public License along with this library; if not, write to the
  Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  Boston, MA  02111-1307  USA
*/

package g4p_controls;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.font.FontRenderContext;
import java.awt.font.GraphicAttribute;
import java.awt.font.ImageGraphicAttribute;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.font.TextHitInfo;
import java.awt.font.TextLayout;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.text.AttributedCharacterIterator;
import java.text.AttributedCharacterIterator.Attribute;
import java.text.AttributedString;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;

import processing.core.PApplet;

/**
* This class is used to represent text with attributes. <br>
* It means that you don't have to have the same style of font
* or even the same font face over the whole length of the text. <br>
*
* Most font features can be modified all except the text background
* which is transparent. There is a feature to highlight part of the string
* by having a different background colour but this is used for highlighting
* selected text in GTextField and GTextArea components. <br>
*  It is also used for all controls that use text.
* @author Peter Lager
*
*/
final public class StyledString implements GConstantsInternal, Serializable {

  private static final long serialVersionUID = 3577177485527475867L;

  transient private AttributedString styledText = null;
  transient private ImageGraphicAttribute spacer = null;
  transient private LineBreakMeasurer lineMeasurer = null;
  transient private LinkedList<TextLayoutInfo> linesInfo = new LinkedList<TextLayoutInfo>();
  transient private Font font = null;

  // The plain text to be styled
  private String plainText = "";
  // List of attribute runs to match font
  private LinkedList<AttributeRun> baseStyle = new LinkedList<AttributeRun>();
  // List of attribute runs to be applied over the base style
  private LinkedList<AttributeRun> atrun = new LinkedList<AttributeRun>();

  // The width to break a line
  private int wrapWidth = Integer.MAX_VALUE;
  // Flag to determine whether the text layouts need recalculating
  private boolean invalidLayout = true;
  // Flag to determine whether the actual character string have changed
  private boolean invalidText = true;

  // Base justification
  private boolean justify = false;
  private float justifyRatio = 0.7f;

  // Stats
  private float textHeight = 0;
  private float maxLineLength = 0;
  private float maxLineHeight = 0;
  private int nbrLines;

  // These are only used by GTextField and GTextArea to store the start and end positions
  // for selected text when the string is to be saved.
  int startIdx = -1;
  int endIdx = -1;

  /**
   * This is assumed to be a single line of text (i.e. no wrap).
   * EOL characters will be stripped from the text before use.
   *
   * @param startText
   */
  public StyledString(String startText){
    plainText = removeSingleSpacingFromPlainText(startText);
    spacer = getParagraghSpacer(1); //  safety
    // Get rid of any EOLs
    styledText = new AttributedString(plainText);
    applyAttributes();
    invalidText = true;
    invalidLayout = true;
  }

  /**
   * Supports multiple lines of text wrapped on word boundaries. <br>
   *
   * @param startText
   * @param wrapWidth
   */
  public StyledString(String startText, int wrapWidth){
    if(wrapWidth > 0 && wrapWidth < Integer.MAX_VALUE)
      this.wrapWidth = wrapWidth;
    plainText = (wrapWidth == Integer.MAX_VALUE) ? removeSingleSpacingFromPlainText(startText) : removeDoubleSpacingFromPlainText(startText);
    spacer = getParagraghSpacer(this.wrapWidth);
    styledText = new AttributedString(plainText);
    styledText = insertParagraphMarkers(plainText, styledText);
    applyAttributes();
    invalidText = true;
    invalidLayout = true;
  }

  /**
   * Converts this StyledString from multi-line to single-line by replacing all EOL
   * characters with the space character
   * for paragraphs
   * @param ptext
   * @param as
   * @return the converted string
   */
  StyledString convertToSingleLineText(){
    // Make sure we have something to work with.
    if(styledText == null || plainText == null){
      plainText = "";
      styledText = new AttributedString(plainText);
    }
    else {
      // Scan through plain text and for each EOL replace the paragraph spacer from
      // the attributed string (styledText).
      int fromIndex = plainText.indexOf('\n', 0);
      if(fromIndex >= 0){
        while(fromIndex >= 0){
          try { // if text == "\n" then an exception is thrown
            styledText.addAttribute(TextAttribute.CHAR_REPLACEMENT, ' ', fromIndex, fromIndex + 1);
            fromIndex = plainText.indexOf('\n', fromIndex + 1);
          }
          catch(Exception excp){
            break;
          }
        }
        // Finally replace all EOL in the plainText
        plainText = plainText.replace('\n', ' ');
      }
    }
    wrapWidth = Integer.MAX_VALUE;
    return this;
  }

  /**
   * Get the plain text as a String. Any line breaks will kept and will
   * be represented by the character 'backslash n' <br>
   * @return the associated plain text
   */
  public String getPlainText(){
    return plainText;
  }

  /**
   * Get the number of characters in this styled string
   */
  public int length(){
    return plainText.length();
  }

  /*
   * Set the font face to be used. This will be system specific
   * but all Java implementations support the following logical
   * fonts. <br>
   * Dialog    <br>
   * DialogInput  <br>
   * Monospaced  <br>
   * Serif    <br>
   * SansSerif  <br>
   * and the physical font faces for <br>
   * Lucinda    <br>
   * Requesting a font family that does not existing on the system
   * will be ignored. <br>
   * Note the class attribute fontFamilies is an array of all
   * @param family
   * @return true if the font was found and different from the current font family
   */

  /**
   * Text can be either left or fully justified.
   * @param justify true for full justification
   */
  public void setJustify(boolean justify){
    if(this.justify != justify){
      this.justify = justify;
      invalidLayout = true;
    }
  }

  /**
   * Justify only if the line has sufficient text to do so.
   *
   * @param jRatio ratio of text length to visibleWidth
   */
  public void setJustifyRatio(float jRatio){
    if(justifyRatio != jRatio){
      justifyRatio = jRatio;
      if(justify)
        invalidLayout = true;
    }
  }

  /**
   * This class uses transparent images to simulate end/starting positions
   * for paragraphs
   * @param ptext
   * @param as
   * @return the styled string with paragraph marker images embedded
   */
  private AttributedString insertParagraphMarkers(String ptext, AttributedString as ){
    if(ptext != null && ptext.length() > 0)
      plainText = ptext;
    int fromIndex = ptext.indexOf('\n', 0);
    while(fromIndex >= 0){
      try { // if text == "\n" then an exception is thrown
        as.addAttribute(TextAttribute.CHAR_REPLACEMENT, spacer, fromIndex, fromIndex + 1);
        fromIndex = ptext.indexOf('\n', fromIndex + 1);
      }
      catch(Exception excp){
        break;
      }
    }
    return as;
  }

  /**
   * Add an attribute that affects the whole length of the string.
   * @param type attribute type
   * @param value attribute value
   */
  public void addAttribute(Attribute type, Object value){
    addAttribute(type, value, Integer.MIN_VALUE, Integer.MAX_VALUE);
  }

  /**
   * Set charStart and charEnd to <0 if full length.
   *
   * @param type
   * @param value
   * @param charStart
   * @param charEnd
   */
  public void addAttribute(Attribute type, Object value, int charStart, int charEnd){
    AttributeRun ar = new AttributeRun(type, value, charStart, charEnd);
    // If we already have attributes try and rationalise the number by merging
    // runs if possible and removing runs that no longer have a visible effect.
    if(atrun.size() > 0){
      ListIterator<AttributeRun> iter = atrun.listIterator(atrun.size());
      while(iter.hasPrevious()){
        AttributeRun a = iter.previous();
        int action = ar.intersectionWith(a);
        int intersect = action & I_MODES;
        int combiMode = action & COMBI_MODES;
        if(combiMode == MERGE_RUNS){
          switch(intersect){
          case I_TL:
          case I_CL:
              ar.start = a.start;
              iter.remove();
            break;
          case I_TR:
          case I_CR:
              ar.end = a.end;
              iter.remove();
            break;
          }
        }
        else if(combiMode == CLIP_RUN){
          switch(intersect){
          case I_CL:
            a.end = ar.start;
            break;
          case I_CR:
            a.start = ar.end;
            break;
          }
        }
        switch(intersect){
        case I_INSIDE:
          iter.remove();
          break;
        case I_COVERED:
          ar = null;
          break;       
        }
      }
    }
    // If the run is still effective then add it
    if(ar != null)
      atrun.addLast(ar);
    applyAttributes();
    invalidLayout = true;
  }

 
  /**
   * Must call this method to apply
   */
  private void applyAttributes(){
    if(plainText.length() > 0){
      for(AttributeRun bsar : baseStyle)
        styledText.addAttribute(bsar.atype, bsar.value);
      Iterator<AttributeRun> iter = atrun.iterator();
      AttributeRun ar;
      while(iter.hasNext()){
        ar = iter.next();
        if(ar.end == Integer.MAX_VALUE)
          styledText.addAttribute(ar.atype, ar.value);
        else {
          // If an attribute run fails do not try and fix it - dump it
          try {
              styledText.addAttribute(ar.atype, ar.value, ar.start, ar.end);
          }
          catch(Exception excp){
            System.out.println("Dumping " + ar);
            iter.remove();
          }
        }
      }
    }
    invalidLayout = true;
  }

  /**
   * Clears all attributes from start to end-1
   * @param start
   * @param end
   */
  public void clearAttributes(int start, int end){
    ListIterator<AttributeRun> iter = atrun.listIterator();
    AttributeRun ar;
    while(iter.hasNext()){
      ar = iter.next();
      // Make sure we have intersection
      if( !(start >= ar.end && end >= ar.start )){
        // Find the limits to clear
        int s = Math.max(start, ar.start);
        int e = Math.min(end, ar.end);
        if(ar.start == s && ar.end == e)
          iter.remove();
        else if(ar.start == s) // clear style from beginning
          ar.start = e;
        else if(ar.end == e) // clear style from end
          ar.end = s;
        else // Split attribute run
          AttributeRun ar2 = new AttributeRun(ar.atype, ar.value, e, ar.end);
          iter.add(ar2);
          ar.end = s;
        }
      }
    }
    invalidText = true;
  }
 
  /**
   * Removes all styling from the string.
   *
   */
  public void clearAllAttributes(){
    atrun.clear();
    invalidText = true;
  }

  /**
   * Insert 1 or more characters into the string. The inserted text will first be made
   * safe by removing any inappropriate EOL characters. <br>
   * Do not use this method to insert EOL characters, use the <pre>insertEOL(int)</pre>
   * method instead.
   *
   * @param chars
   * @param insertPos the position in the text
   */
  public int insertCharacters(int insertPos, String chars){
    if(chars.length() > 0)
      chars = makeStringSafeForInsert(chars);
    return insertCharactersImpl(insertPos, chars, false);
//    if(chars.length() > 0){
//      int nbrChars = chars.length();
//      if(plainText.equals(" "))
//        plainText = chars;
//      else
//        plainText = plainText.substring(0, insertPos) + chars + plainText.substring(insertPos);
//      insertParagraphMarkers(plainText, styledText);
//      for(AttributeRun ar : atrun){
//        if(ar.end < Integer.MAX_VALUE){
//          if(ar.end >= insertPos){
//            ar.end += nbrChars;
//            if(ar.start >= insertPos)
//              ar.start += nbrChars;
//          }
//        }
//      }
//      invalidText = true;
//    }
//    return chars.length();
  }

  public int insertCharacters(int insertPos, String chars, boolean startNewLine){
    // Ray: modify the code to not call makeStringSafeForInsert as it will
    //      remove EOL characters.
    //if(chars.length() > 0)
    //  chars = makeStringSafeForInsert(chars);
    return insertCharactersImpl(insertPos, chars, startNewLine);
  }
 
  private int insertCharactersImpl(int insertPos, String chars, boolean startNewLine){
    if(chars.length() > 0){
      if(startNewLine)
        chars = "\n" + chars;
      int nbrChars = chars.length();
      if(plainText.equals(" "))
        plainText = chars;
      else
        plainText = plainText.substring(0, insertPos) + chars + plainText.substring(insertPos);
      insertParagraphMarkers(plainText, styledText);
      for(AttributeRun ar : atrun){
        if(ar.end < Integer.MAX_VALUE){
          if(ar.end >= insertPos){
            ar.end += nbrChars;
            if(ar.start >= insertPos)
              ar.start += nbrChars;
          }
        }
      }
      invalidText = true;
    }
    return chars.length();   
  }
 
  /**
   * This is ONLY used when multiple characters are to be inserted. <br>
   * If it is single line text i.e. no wrapping then it removes all EOLs
   * If it is multiple line spacing it will reduce all double EOLs to single
   * EOLs and remove any EOLs at the start or end of the string.
   *
   * @param chars
   * @return a string that is safe for inserting
   */
  private String makeStringSafeForInsert(String chars){
    // Get rid of single / double line spacing
    if(chars.length() > 0){
      if(wrapWidth == Integer.MAX_VALUE) // no wrapping remove all
        chars = removeSingleSpacingFromPlainText(chars);
      else {
        chars = removeDoubleSpacingFromPlainText(chars); // wrapping remove double spacing
        // no remove EOL at ends of string
        while(chars.length() > 0 && chars.charAt(0) == '\n')
          chars = chars.substring(1);
        while(chars.length() > 0 && chars.charAt(chars.length() - 1) == '\n')
          chars = chars.substring(0, chars.length() - 1);
      }
    }
    return chars;
  }
 
  /**
   * Use this method to insert an EOL character.
   * @param insertPos index position to insert EOL
   * @return true if an EOL was inserted into the string
   */
  public boolean insertEOL(int insertPos){
    if(wrapWidth != Integer.MAX_VALUE){
      if(insertPos > 0 && plainText.charAt(insertPos-1) == '\n')
        return false;
      if(insertPos < plainText.length()-1 && plainText.charAt(insertPos+1) == '\n'){
        return false;
      }
      plainText = plainText.substring(0, insertPos) + "\n" + plainText.substring(insertPos);
      insertParagraphMarkers(plainText, styledText);
      for(AttributeRun ar : atrun){
        if(ar.end < Integer.MAX_VALUE){
          if(ar.end >= insertPos){
            ar.end += 1;
            if(ar.start >= insertPos)
              ar.start += 1;
          }
        }
      }
      invalidText = true;
      return true;
    }
    return false;
  }
 
 
  /**
   * Remove a number of characters from the string
   *
   * @param nbrToRemove number of characters to remove
   * @param fromPos start location for removal
   * @return true if the deletion was successful else false
   */
  public boolean deleteCharacters(int fromPos, int nbrToRemove){
    if(fromPos < 0 || fromPos + nbrToRemove > plainText.length())
      return false;
    /*
     * If the character preceding the selection and the character immediately after the selection
     * are both EOLs then increment the number of characters to be deleted
     */
    if(wrapWidth != Integer.MAX_VALUE){
      if(fromPos > 0 && fromPos + nbrToRemove < plainText.length() - 1){
        if(plainText.charAt(fromPos) == '\n' && plainText.charAt(fromPos + nbrToRemove) == '\n'){
          nbrToRemove++;
        }
      }
    }
    if(fromPos != 0)
      plainText = plainText.substring(0, fromPos) + plainText.substring(fromPos + nbrToRemove);
    else
      plainText = plainText.substring(fromPos + nbrToRemove);
    // For wrappable text make sure we have not created
    if(plainText.length() == 0){
      atrun.clear();
      styledText = null;
    }
    else {
      ListIterator<AttributeRun> iter = atrun.listIterator(atrun.size());
      AttributeRun ar;
      while(iter.hasPrevious()){
        ar = iter.previous();
        if(ar.end < Integer.MAX_VALUE){
          // Only need to worry about this if the run ends after the deletion point
          if(ar.end >= fromPos){
            int lastPos = fromPos + nbrToRemove;
            // Deletion removes entire run
            if(fromPos <= ar.start && lastPos >= ar.end){
              iter.remove();
              continue;
            }
            // Deletion fits entirely within the run
            if(fromPos > ar.start && lastPos < ar.end){
              ar.end -= nbrToRemove;
              continue;
            }
            // Now we have overlap either at one end of the run
            // Overlap at start of run?
            if(fromPos <= ar.start){
              ar.start = fromPos;
              ar.end -= nbrToRemove;
              continue;
            }
            // Overlap at end of run?
            if(lastPos >= ar.end){
              ar.end = fromPos;
              continue;
            }
            System.out.println("This run was not modified");
            System.out.println("Run from " + ar.start + " to " + ar.end);
            System.out.println("Delete from " + fromPos + " To " + lastPos + "  (" + nbrToRemove + " to remove)");
          }
        }   
      }
    }
    invalidText = true;
    return true;
  }

  public void setFont(Font a_font){
    if(a_font != null){
      font = a_font;
      baseStyle.clear();
      baseStyle.add(new AttributeRun(TextAttribute.FAMILY, font.getFamily()));
      baseStyle.add(new AttributeRun(TextAttribute.SIZE, font.getSize()));
      if(font.isBold())
        baseStyle.add(new AttributeRun(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD));
      if(font.isItalic())
        baseStyle.add(new AttributeRun(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE))
      invalidText = true;
    }
  }

  /**
   * Get the text layouts for display if the string has changed since last call
   * to this method regenerate them.
   *
   * @param g2d Graphics2D display context
   * @return a list of text layouts for rendering
   */
  public LinkedList<TextLayoutInfo> getLines(Graphics2D g2d){
    if(font != g2d.getFont()){
      setFont(g2d.getFont());
      invalidText = true;
    }
    if(invalidText){
      styledText = new AttributedString(plainText);
      styledText = insertParagraphMarkers(plainText, styledText);
      applyAttributes();
      invalidText = false;
      invalidLayout = true;
    }
    if(invalidLayout){
      linesInfo.clear();
      if(plainText.length() > 0){
        textHeight = 0;
        maxLineLength = 0;
        maxLineHeight = 0;
        nbrLines = 0;
        AttributedCharacterIterator paragraph = styledText.getIterator(null, 0, plainText.length());
        FontRenderContext frc = g2d.getFontRenderContext();
        lineMeasurer = new LineBreakMeasurer(paragraph, frc);
        float yposinpara = 0;
        int charssofar = 0;
        while (lineMeasurer.getPosition() < plainText.length()) {
          TextLayout layout = lineMeasurer.nextLayout(wrapWidth);
          float advance = layout.getVisibleAdvance();
          if(justify){
            if(justify && advance > justifyRatio * wrapWidth){
              //System.out.println(layout.getVisibleAdvance() + "  " + breakWidth + "  "+ layout.get);
              // If advance > breakWidth then we have a line break
              float jw = (advance > wrapWidth) ? advance - wrapWidth : wrapWidth;
              layout = layout.getJustifiedLayout(jw);
            }
          }
          // Remember the longest and tallest value for a layout so far.
          float lh = getHeight(layout);
          if(lh > maxLineHeight)
            maxLineHeight = lh;
          textHeight += lh;
          if(advance <= wrapWidth && advance > maxLineLength)
            maxLineLength = advance;

          // Store layout and line info
          linesInfo.add(new TextLayoutInfo(nbrLines, layout, charssofar, layout.getCharacterCount(), yposinpara));
          charssofar += layout.getCharacterCount();
          yposinpara += lh;
          nbrLines++;
        }
      }
      invalidLayout = false;
    }
    return linesInfo;
  }

  /**
   * Get the number of lines in the layout
   */
  public int getNbrLines(){
    return nbrLines;
  }

  /**
   * Return the height of the text line(s)
   */
  public float getTextAreaHeight(){
    return textHeight;
  }

  /**
   * Return the length of the longest line.
   */
  public float getMaxLineLength(){
    return maxLineLength;
  }

  /**
   * Get the height of the tallest line
   */
  public float getMaxLineHeight(){
    return maxLineHeight;
  }

  /**
   * Get the height of the given TextLayout
   * @param layout
   * @return the height of a given text layout
   */
  private float getHeight(TextLayout layout){
    return layout.getAscent() +layout.getDescent() + layout.getLeading();
  }

  /**
   * Get the break width used to create the lines.
   */
  public int getWrapWidth(){
    return wrapWidth;
  }

  /**
   * Set the maximum width of a line.
   * @param wrapWidth
   */
  public void setWrapWidth(int wrapWidth){
    if(this.wrapWidth != wrapWidth){
      this.wrapWidth = wrapWidth;
      invalidLayout = true;
    }
  }

  TextLayoutHitInfo calculateFromXY(Graphics2D g2d, float px, float py){
    TextHitInfo thi = null;
    TextLayoutInfo tli = null;
    TextLayoutHitInfo tlhi = null;
    if(invalidLayout)
      getLines(g2d);
    if(px < 0) px = 0;
    if(py < 0) py = 0;
    tli = getTLIforYpos(py);
    // Correct py to match layout's upper-left bounds
    py -= tli.yPosInPara;
    // get hit
    thi = tli.layout.hitTestChar(px,py);
    tlhi = new TextLayoutHitInfo(tli, thi);
    return tlhi;
  }

  /**
   * Get a layout based on line number
   * @param ln line number
   * @return text layout info for the line ln
   */
  TextLayoutInfo getTLIforLineNo(int ln){
    return linesInfo.get(ln);
  }

  /**
   * This will always return a layout provide there is some text.
   * @param y Must be >= 0
   * @return the first layout where y is above the upper layout bounds
   */
  TextLayoutInfo getTLIforYpos(float y){
    TextLayoutInfo tli = null;
    if(!linesInfo.isEmpty()){
      for(int i = linesInfo.size()-1; i >= 0; i--){
        tli = linesInfo.get(i);
        if(tli.yPosInPara <= y)
          break;
      }
    }
    return tli;
  }

  /**
   * This will always return a layout provided charNo >= 0. <br>
   *
   * If charNo > than the index of the last character in the plain text then this
   * should be corrected to the last character in the layout by the caller.
   *
   * @param charNo the character position in text (must be >= 0)
   * @return the first layout where c is greater that the layout's start char index.
   */
  TextLayoutInfo getTLIforCharNo(int charNo){
    TextLayoutInfo tli = null;
    if(!linesInfo.isEmpty()){
      for(int i = linesInfo.size()-1; i >= 0; i--){
        tli = linesInfo.get(i);
        if(tli.startCharIndex < charNo)
          break;
      }
    }
    return tli;
  }

  /**
   * Ensure we do not have blank lines by replacing double EOL characters by
   * single EOL until there are only single EOLs. <br>
   * Using replaceAll on its own will not work because EOL/EOL/EOL would
   * become EOL/EOL not the single EOL required.
   *
   */
  private String removeDoubleSpacingFromPlainText(String chars){
    while(chars.indexOf("\n\n") >= 0){
      invalidText = true;
      chars = chars.replaceAll("\n\n", "\n");
    }
    return chars;
  }

  /**
   * Remove all EOL characters from the string. This is necessary if the string
   * is for a single line component.
   * @param chars the string to use
   * @return the string with all EOLs removed
   */
  private String removeSingleSpacingFromPlainText(String chars){
    while(chars.indexOf("\n") >= 0){
      invalidText = true;
      chars = chars.replaceAll("\n", "");
    }
    return chars;
  }
 
  /**
   * Create a graphic image character to simulate paragraph breaks
   *
   * @param ww
   * @return a blank image to manage paragraph ends.
   */
  private ImageGraphicAttribute getParagraghSpacer(int ww){
    if(ww == Integer.MAX_VALUE)
      ww = 1;
    BufferedImage img = new BufferedImage(ww, 10, BufferedImage.TYPE_INT_ARGB);
    Graphics g = img.getGraphics();
    g.setColor(new Color(255, 255, 255, 0));
    g.fillRect(0,0,img.getWidth(), img.getHeight());
    return new ImageGraphicAttribute(img, GraphicAttribute.TOP_ALIGNMENT);
  }


  /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
   * Serialisation routines to save/restore the StyledString to disc.
   */

  /**
   * Save the named StyleString in the named file.
   *
   * @param papp
   * @param ss the styled string
   * @param fname
   */
  public static void save(PApplet papp, StyledString ss, String fname){
    OutputStream os;
    ObjectOutputStream oos;
    try {
      os = papp.createOutput(fname);
      oos = new ObjectOutputStream(os);
      oos.writeObject(ss);
      os.close();
      oos.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  /**
   * Load and return a StyledString object from the given file.
   *
   * @param papp
   * @param fname the filename of the StyledString
   */
  public static StyledString load(PApplet papp, String fname){
    StyledString ss = null;
    InputStream is;
    ObjectInputStream ios; 
    try {
      is = papp.createInput(fname);
      ios = new ObjectInputStream(is);
      ss = (StyledString) ios.readObject();
      is.close();
      ios.close();
    } catch (IOException e) {
      e.printStackTrace();
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    }
    return ss;
  }


  private void readObject(ObjectInputStream ois)
  throws ClassNotFoundException, IOException {
    ois.defaultReadObject();
    // Recreate transient elements
    spacer = getParagraghSpacer(wrapWidth);
    styledText = new AttributedString(plainText);
    styledText = insertParagraphMarkers(plainText, styledText);
    linesInfo = new LinkedList<TextLayoutInfo>();
    applyAttributes();
  }

  /**
   * For multi-line text, the TextHitInfo class is not enough. We also need
   * information about the layout so that the caret(s) can be drawn.
   *
   * @author Peter Lager
   *
   */
  static public class TextLayoutHitInfo implements Comparable<TextLayoutHitInfo>{
    public TextLayoutInfo tli;
    public TextHitInfo thi;

   
    public TextLayoutHitInfo() {
      this.tli = null;
      this.thi = null;
    }
   
    /**
     * @param tli
     * @param thi
     */
    public TextLayoutHitInfo(TextLayoutInfo tli, TextHitInfo thi) {
      this.tli = tli;
      this.thi = thi;
    }

    /**
     * Copy constructor
     * @param tlhi
     */
    public TextLayoutHitInfo(TextLayoutHitInfo tlhi){
      tli = tlhi.tli;
      thi = tlhi.thi;
    }

    public void copyFrom(TextLayoutHitInfo other){
      this.tli = other.tli;
      this.thi = other.thi;
    }

    public void setInfo(TextLayoutInfo tli, TextHitInfo thi) {
      this.tli = tli;
      this.thi = thi;
    }

    public int compareTo(TextLayoutHitInfo other) {
      int layoutComparison = tli.compareTo(other.tli);
      if(layoutComparison != 0)
        return layoutComparison; // Different layouts so return comparison
      // Same layout SO test hit info
      if(thi.equals(other.thi))
        return 0;
      // Same layout different hit info SO test char index
      if(thi.getCharIndex() != other.thi.getCharIndex()){
        // Different current chars so order on position
        return (thi.getCharIndex() < other.thi.getCharIndex() ? -1 : 1);
      }
      // Same layout same char different edge hit SO test on edge hit
      return (thi.isLeadingEdge() ? -1 : 1);     
    }

    public String toString(){
      StringBuilder s = new StringBuilder(tli.toString());
      s.append("  Hit char = " + thi.getCharIndex());
      return new String(s);     
    }
  }

  /**
   * Class to hold information about a text layout. This class helps simplify the
   * algorithms needed for multi-line text.
   *
   * @author Peter Lager
   *
   */
  static public class TextLayoutInfo implements Comparable<TextLayoutInfo> {
    public TextLayout layout;    // The matching layout
    public int lineNo;        // The line number
    public int startCharIndex;    // Position of the first char in text
    public int nbrChars;      // Number of chars in this layout
    public float yPosInPara;     // Top-left corner of bounds

    /**
     * @param startCharIndex
     * @param nbrChars
     * @param yPosInPara
     */
    public TextLayoutInfo(int lineNo, TextLayout layout, int startCharIndex, int nbrChars, float yPosInPara) {
      this.lineNo = lineNo;
      this.layout  = layout;
      this.startCharIndex = startCharIndex;
      this.nbrChars = nbrChars;
      this.yPosInPara = yPosInPara;
    }

    public int compareTo(TextLayoutInfo other) {
      if(lineNo == other.lineNo)
        return 0;
      return (startCharIndex < other.startCharIndex) ? -1 : 1;
    }

    public String toString(){
      StringBuilder s = new StringBuilder("{ Line no = " + lineNo + "    starts @ char pos " + startCharIndex);
      s.append("  last index " + (startCharIndex+nbrChars+1));
      s.append("  (" + nbrChars +")  ");
      return new String(s);
    }
  }

  /**
   * Since most of the Java classes associated with AttributedString
   * are immutable with virtually no public methods this class represents
   * an attribute to be applied. <br>
   *
   * This class is only used from within StyledString.
   *
   * @author Peter Lager
   *
   */
  private class AttributeRun implements Serializable {

    private static final long serialVersionUID = -8401062069478890163L;
   
    public Attribute atype;
    public Object value;
    public Integer start;
    public Integer end;


    /**
     * The attribute and value to be applied over the whole string
     * @param atype
     * @param value
     */
    public AttributeRun(Attribute atype, Object value) {
      this.atype = atype;
      this.value = value;
      this.start = Integer.MIN_VALUE;
      this.end = Integer.MAX_VALUE;
    }

    /**
     * The attribute and value to be applied over the given range
     * @param atype
     * @param value
     * @param start
     * @param end
     */
    public AttributeRun(Attribute atype, Object value, int start, int end) {
      this.atype = atype;
      this.value = value;
      this.start = start;
      this.end = end;
    }

    /**
     * If possible merge the two runs or crop the prevRun run.
     *
     * If both runs have the same attribute type and the represent
     * the same location and size in the text then the intersection
     * mode will be MM_SURROUNDS rather than MM_SURROUNDED because
     * 'this' is the attribute being added.
     * @param m
     * @param s
     * @return
     */
    private int intersectionWith(AttributeRun ar){
      // Different attribute types?
      if(atype != ar.atype)
        return I_NONE;
      // Check for combination mode
      int combi_mode = (value.equals(ar.value)) ? MERGE_RUNS : CLIP_RUN;
      int sdx = 4, edx = 0;
      // Start index
      if(ar.start < start)
        sdx = 0;
      else if(ar.start == start)
        sdx = 1;
      else if (ar.start < end)
        sdx = 2;
      else if(ar.start == end)
        sdx = 3;
      if(sdx < 4){
        if(ar.end > end)
          edx = 4;
        else if(ar.end == end)
          edx = 3;
        else if(ar.end > start)
          edx = 2;
        else if(ar.end == start)
          edx = 1;
      }
      combi_mode |= grid[sdx][edx];
      return combi_mode;
    }
   
    public String toString(){
      String s = atype.toString() + "  value = " + value.toString() + "  from " + start + "   to " + end;
      return s;
    }

  }  // End of AttributeRun class

}
TOP

Related Classes of g4p_controls.StyledString$AttributeRun

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.
div> script>